package gek.uni.diplom.ocl.example.visitors;

import java.util.ArrayList;
import java.util.List;

import org.eclipse.emf.ecore.EObject;
import org.eclipse.emf.ecore.util.EcoreUtil;
import org.eclipse.ocl.expressions.BooleanLiteralExp;
import org.eclipse.ocl.expressions.IntegerLiteralExp;
import org.eclipse.ocl.expressions.IterateExp;
import org.eclipse.ocl.expressions.IteratorExp;
import org.eclipse.ocl.expressions.LiteralExp;
import org.eclipse.ocl.expressions.OCLExpression;
import org.eclipse.ocl.expressions.OperationCallExp;
import org.eclipse.ocl.expressions.PropertyCallExp;
import org.eclipse.ocl.expressions.RealLiteralExp;
import org.eclipse.ocl.expressions.StringLiteralExp;
import org.eclipse.ocl.expressions.TypeExp;
import org.eclipse.ocl.expressions.Variable;
import org.eclipse.ocl.expressions.VariableExp;
import org.eclipse.ocl.utilities.AbstractVisitor;
import org.eclipse.ocl.utilities.PredefinedType;

/**
 * Посетитель OCL выражения, который раскрывает скобки в выражении
 * 
 * @author Gek
 * 
 * @param <C>
 * @param <O>
 * @param <P>
 * @param <EL>
 * @param <PM>
 * @param <S>
 * @param <COA>
 * @param <SSA>
 * @param <CT>
 */

//Ожидаемый результат 5*4-5*3*2
public class OpenBracketVisitor<C, O, P, EL, PM, S, COA, SSA, CT> extends
    AbstractVisitor<OCLExpression<C>, C, O, P, EL, PM, S, COA, SSA, CT> {

  public static String LEFT_SIDE_KEY = "left";
  public static String RIGHT_SIDE_KEY = "right";

  @Override
  public OCLExpression<C> visitOperationCallExp(OperationCallExp<C, O> callExp) {
    int operationCode = callExp.getOperationCode();
    switch (operationCode) {
    case PredefinedType.PLUS:
      System.out.println("Plus-VISIT");
      break;
    case PredefinedType.TIMES:
      System.out.println("Multy-VISIT");
      break;
    default:
      break;
    }
    return super.visitOperationCallExp(callExp);
  }

  @Override
  protected OCLExpression<C> handlePropertyCallExp(
      PropertyCallExp<C, P> callExp, OCLExpression<C> sourceResult,
      List<OCLExpression<C>> qualifierResults) {
    return callExp;
  }

  @SuppressWarnings("unchecked")
  @Override
  protected OCLExpression<C> handleOperationCallExp(
      OperationCallExp<C, O> callExp, OCLExpression<C> sourceResult,
      List<OCLExpression<C>> argumentResults) {

    int opCode = callExp.getOperationCode();
    switch (opCode) {
    case PredefinedType.OCL_AS_TYPE:
    case PredefinedType.OCL_IS_IN_STATE:
    case PredefinedType.OCL_IS_INVALID:
    case PredefinedType.OCL_IS_KIND_OF:
    case PredefinedType.OCL_IS_NEW:
    case PredefinedType.OCL_IS_TYPE_OF:
    case PredefinedType.OCL_IS_UNDEFINED:
    case PredefinedType.INCLUDES:
    case PredefinedType.INCLUDES_ALL:
    case PredefinedType.EXCLUDES:
    case PredefinedType.EXCLUDES_ALL:
    case PredefinedType.HAS_RETURNED:
    case PredefinedType.IS_SIGNAL_SENT:
    case PredefinedType.IS_OPERATION_CALL:
    case PredefinedType.IS_EMPTY:
    case PredefinedType.NOT_EMPTY:
    case PredefinedType.IS_UNIQUE:
      
    case PredefinedType.EQUAL:
    case PredefinedType.NOT_EQUAL:
    case PredefinedType.LESS_THAN:
    case PredefinedType.LESS_THAN_EQUAL:
    case PredefinedType.GREATER_THAN:
    case PredefinedType.GREATER_THAN_EQUAL:
      OperationCallExp<C, O> result = (OperationCallExp<C, O>) EcoreUtil.copy(callExp);
      result.setSource((OCLExpression<C>) EcoreUtil.copy(sourceResult));
      result.getArgument().clear();
      try{
        result.getArgument().add((OCLExpression<C>) EcoreUtil.copy(argumentResults.get(0)));
      } catch (Exception e) {
      }
      return result;
      // return createOrderResult(callExp, sourceResult, argumentResults);
      // break;

    case PredefinedType.TIMES:
    case PredefinedType.DIVIDE:
      return createBracketExpression(callExp, sourceResult, argumentResults);
    case PredefinedType.PLUS:
    case PredefinedType.MINUS: {
      OperationCallExp<C, O> res = (OperationCallExp<C, O>) EcoreUtil
          .copy(callExp);
      res.setSource((OCLExpression<C>) EcoreUtil.copy(sourceResult));
      res.getArgument().clear();
      for (EObject arg : argumentResults) {
        res.getArgument().add((OCLExpression<C>) EcoreUtil.copy(arg));
      }
      return res;
    }
      // return createStandartResult(callExp, sourceResult,
      // argumentResults);
      // break;

    default:
      OperationCallExp<C, O> res = (OperationCallExp<C, O>) EcoreUtil.copy(callExp);
      res.setSource((OCLExpression<C>) EcoreUtil.copy(sourceResult));
      res.getArgument().clear();
      try{
        res.getArgument().add((OCLExpression<C>) EcoreUtil.copy(argumentResults.get(0)));
      } catch (Exception e) {
      }
      return res;
    }
  }

  @Override
  public OCLExpression<C> visitIntegerLiteralExp(IntegerLiteralExp<C> literalExp) {
    return literalExp;
  }
  
  @Override
  public OCLExpression<C> visitRealLiteralExp(RealLiteralExp<C> literalExp) {
    return literalExp;
  }

  @Override
  public OCLExpression<C> visitVariableExp(VariableExp<C, PM> v) {
    return v;
  }
  
  @Override
  public OCLExpression<C> visitBooleanLiteralExp(BooleanLiteralExp<C> literalExp) {
    return literalExp;
  }
  
  @Override
  public OCLExpression<C> visitStringLiteralExp(StringLiteralExp<C> literalExp) {
    return literalExp;
  }
  
  @Override
  public OCLExpression<C> visitTypeExp(TypeExp<C> t) {
    return t;
  }
  
  @Override
  protected OCLExpression<C> handleIteratorExp(IteratorExp<C, PM> callExp,
      OCLExpression<C> sourceResult, List<OCLExpression<C>> variableResults,
      OCLExpression<C> bodyResult) {
    return (OCLExpression<C>) EcoreUtil.copy(callExp);
  }
  
  @Override
  protected OCLExpression<C> handleIterateExp(IterateExp<C, PM> callExp,
      OCLExpression<C> sourceResult, List<OCLExpression<C>> variableResults,
      OCLExpression<C> resultResult, OCLExpression<C> bodyResult) {
    // TODO Auto-generated method stub
    return super.handleIterateExp(callExp, sourceResult, variableResults,
        resultResult, bodyResult);
  }
  
  @Override
  protected OCLExpression<C> handleVariable(Variable<C, PM> variable,
      OCLExpression<C> initResult) {
    return super.handleVariable(variable, initResult);
  }
  
  
  /**
   * Раскрывает скобки для выражения вида (a[+-b]*){*|/}(c[+-d]*)
   * 
   * @param callExp
   * @param sourceResult
   * @param argumentResults
   * @return
   */
  @SuppressWarnings("unchecked")
  private OCLExpression<C> createBracketExpression(
      OperationCallExp<C, O> callExp, OCLExpression<C> sourceResult,
      List<OCLExpression<C>> argumentResults) {

    // 5*...
    if (false == sourceResult instanceof OperationCallExp) {
      // 5*(a+...)
      if (argumentResults.get(0) instanceof OperationCallExp) {
        LiteralExp<C> literal = (LiteralExp<C>) sourceResult;
        OperationCallExp<C, O> argExp = (OperationCallExp<C, O>) argumentResults
            .get(0);
        return createLiteralMultyCallExpression(callExp, literal, argExp);
      } else
        // 5*5
        return callExp;
    } else if (sourceResult instanceof OperationCallExp) {// (a+...)*...
      // (a+...)*5
      if (argumentResults.get(0) instanceof LiteralExp) {
        LiteralExp<C> literal = (LiteralExp<C>) argumentResults.get(0);
        OperationCallExp<C, O> argExp = (OperationCallExp<C, O>) sourceResult;
        return createCallMultyLiteralExpression(callExp, argExp, literal);
      } else {
        // (a+b+c)*(d+e+f)
        OperationCallExp<C, O> leftExp = (OperationCallExp<C, O>) sourceResult;
        OperationCallExp<C, O> rightExp = (OperationCallExp<C, O>) argumentResults
            .get(0);
        return createCallMultyCallExpression(callExp, leftExp, rightExp);
      }
    }
    return null;
  }

  /**
   * Раскрывает скобки для выражений вида (a+b+c)*(d+e+f)
   * 
   * @param callExp
   * @param leftExp
   * @param rightExp
   * @return
   */
  private OCLExpression<C> createCallMultyCallExpression(
      OperationCallExp<C, O> callExp, OperationCallExp<C, O> leftExp,
      OperationCallExp<C, O> rightExp) {

    OCLExpression<C> leftSource = leftExp.getSource();
    OperationCallExp<C, O> res = (OperationCallExp<C, O>) EcoreUtil
        .copy(leftExp);

    List<OCLExpression<C>> argumentResults = new ArrayList<OCLExpression<C>>();
    argumentResults.add(rightExp);
    res
        .setSource(createBracketExpression(callExp, leftSource, argumentResults));

    res.getArgument().clear();
    res.getArgument().add(
        createBracketExpression(callExp, leftExp.getArgument().get(0),
            argumentResults));
    return res;
  }

  /**
   * Выполняет операцию callExp.getOperation для literal'а и к каждого члена exp<br/>
   * Если callExp описывает операцию умножения, то literal будет умножен на
   * каждый член exp
   * 
   * @param callExp
   * @param literal
   * @param argumentResults
   * @return
   */
  @SuppressWarnings("unchecked")
  private OCLExpression<C> createLiteralMultyCallExpression(
      OperationCallExp<C, O> callExp, LiteralExp<C> literal,
      OperationCallExp<C, O> argExp) {

    Integer opCode = argExp.getOperationCode();
    switch (opCode) {
    case PredefinedType.PLUS:
    case PredefinedType.MINUS:
      break;
    default:
      return createMultyplicityExpression(callExp, literal, argExp);
    }
    OperationCallExp<C, O> result = (OperationCallExp<C, O>) EcoreUtil
        .copy(argExp);

    OCLExpression<C> source = argExp.getSource();

    // Создаем левый аргумент
    if (false == source instanceof OperationCallExp) {
      OperationCallExp<C, O> leftOperand = createMultyplicityExpression(
          callExp, literal, source);
      result.setSource(leftOperand);
    } else {
      OCLExpression<C> leftOperand = createLiteralMultyCallExpression(callExp,
          literal, (OperationCallExp<C, O>) source);
      result.setSource(leftOperand);
    }

    // Создаем правый аргумент
    OCLExpression<C> right = argExp.getArgument().get(0);
    result.getArgument().clear();
    if (right instanceof OperationCallExp) {
      result.getArgument().add(
          createLiteralMultyCallExpression(callExp, literal,
              (OperationCallExp<C, O>) right));
    } else {
      result.getArgument().add(
          createMultyplicityExpression(callExp, literal, right));
    }
    return result;
  }

  /**
   * Раскрывает скобки для выражений вида (a+...)/5
   * 
   * @param callExp
   * @param argExp
   * @param literal
   * @return
   */
  @SuppressWarnings("unchecked")
  private OperationCallExp<C, O> createCallMultyLiteralExpression(
      OperationCallExp<C, O> callExp, OperationCallExp<C, O> argExp,
      LiteralExp<C> literal) {

    int operationCode = argExp.getOperationCode();
    switch (operationCode) {
    case PredefinedType.PLUS:
    case PredefinedType.MINUS:
      break;

    default:
      return createMultyplicityExpression(callExp, argExp, literal);
    }
    OperationCallExp<C, O> result = (OperationCallExp<C, O>) EcoreUtil
        .copy(argExp);
    List<OCLExpression<C>> argumentResult = new ArrayList<OCLExpression<C>>();
    argumentResult.add(literal);
    result.setSource(createBracketExpression(callExp, argExp.getSource(),
        argumentResult));

    argumentResult.clear();
    argumentResult.add(literal);
    result.getArgument().clear();
    result.getArgument().add(
        createBracketExpression(callExp, argExp.getArgument().get(0),
            argumentResult));
    return result;
  }

  @SuppressWarnings("unchecked")
  public OperationCallExp<C, O> createMultyplicityExpression(
      OperationCallExp<C, O> baseExpression, OCLExpression<C> left,
      OCLExpression<C> right) {
    OperationCallExp<C, O> result = (OperationCallExp<C, O>) EcoreUtil
        .copy(baseExpression);
    result.setSource((OCLExpression<C>) EcoreUtil.copy(left));
    result.getArgument().clear();
    result.getArgument().add((OCLExpression<C>) EcoreUtil.copy(right));
    return result;
  }
}
